Backend#274
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Pull request overview
This PR removes NextAuth-based authentication from the Next.js app and switches the frontend to rely on a backend Sa-Token auth flow, with Next.js acting as a proxy for auth-related endpoints. It also updates Prisma models to decouple legacy NextAuth tables from the new user_accounts identity model and to persist chat/analytics user attribution via BigInt IDs.
Changes:
- Removed NextAuth integration (config, API route, providers) and replaced client auth state with a new
AuthProvider/useAuthhook backed by/auth/me. - Added Next.js rewrites to proxy OAuth callback and
/auth/*routes to the backend, and updated UI components/pages to use the new auth flow. - Updated Prisma schema so
Chat.userId/AnalyticsEvent.userIduseBigIntand no longer relate to the legacyuserstable.
Reviewed changes
Copilot reviewed 22 out of 23 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| proxy.ts | Removed previous auth proxy export/config. |
| auth.ts | Removed NextAuth initialization. |
| auth.config.ts | Removed NextAuth configuration/callbacks. |
| app/api/auth/[...nextauth]/route.ts | Removed NextAuth API handlers route. |
| package.json | Removed next-auth dependency. |
| pnpm-lock.yaml | Lockfile updates reflecting removed next-auth. |
| next.config.mjs | Added rewrites for backend auth/callback proxying. |
| lib/use-auth.tsx | Added client auth context/hook reading token and calling /auth/me. |
| lib/server-auth.ts | Added server helper to resolve user ID via backend /auth/me. |
| prisma/schema.prisma | Annotated legacy NextAuth tables; switched chat/analytics userId to BigInt and removed legacy relations. |
| app/layout.tsx | Replaced SessionProvider with AuthProvider. |
| app/login/page.tsx | Simplified login page to use backend OAuth entrypoint. |
| app/editor/page.tsx | Switched editor gatekeeping to client-side auth. |
| app/editor/EditorPageClient.tsx | Switched from NextAuth session to UserView and added token header for uploads. |
| app/components/SignInButton.tsx | Redirects to backend OAuth render endpoint. |
| app/components/Header.tsx | Moved auth UI to client AuthNav. |
| app/components/AuthNav.tsx | Uses useAuth() to render sign-in vs user menu. |
| app/components/UserMenu.tsx | Uses injected logout() instead of NextAuth signOut. |
| app/components/UmamiIdentity.tsx | Uses useAuth() for Umami identify fields. |
| app/components/DocsAssistant.tsx | Persisted chatId, added per-request x-satoken, and removed client-sent userId. |
| app/api/upload/route.ts | Validates token via backend /auth/me before issuing upload URL. |
| app/api/chat/route.ts | Added backend streaming proxy fallback and binds persisted chat to resolved BigInt userId. |
| app/api/analytics/route.ts | Resolves userId server-side via token and stores BigInt userId. |
| lib/ai/providers/intern.ts | Dev-only remap to Deepseek for testing. |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // 1. 检查 URL 中是否携带 ?token=xxx(后端 OAuth 登录成功后跳回来时携带) | ||
| const params = new URLSearchParams(window.location.search); | ||
| const urlToken = params.get("token"); | ||
|
|
||
| if (urlToken) { | ||
| // 存入 localStorage | ||
| localStorage.setItem("satoken", urlToken); | ||
| // 用 replaceState 清除 URL 中的 token 参数,避免刷新或分享时 token 泄露 | ||
| params.delete("token"); | ||
| const newSearch = params.toString(); | ||
| const newUrl = | ||
| window.location.pathname + | ||
| (newSearch ? "?" + newSearch : "") + | ||
| window.location.hash; | ||
| window.history.replaceState(null, "", newUrl); | ||
| } |
There was a problem hiding this comment.
The auth token is accepted via the ?token= query param and then stored in localStorage. Even with replaceState, query-string tokens can leak via server/access logs and Referer headers, and localStorage makes the token available to any XSS. Consider switching to an OAuth code exchange (or redirect token in URL fragment) and store the session in an HttpOnly cookie instead of localStorage.
| const controller = new AbortController(); | ||
| const timeoutId = setTimeout(() => controller.abort(), 5000); // 5秒超时 | ||
|
|
||
| // 原封不动把前端的参数丢给 Java | ||
| const proxyRes = await fetch(`${backendUrl}/openai/responses/stream`, { | ||
| method: "POST", | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| "x-satoken": req.headers.get("x-satoken") || "", | ||
| }, | ||
| body: await proxyReq.text(), | ||
| signal: controller.signal, | ||
| }); | ||
|
|
||
| clearTimeout(timeoutId); | ||
|
|
There was a problem hiding this comment.
In the Java-backend proxy attempt, the timeout is only cleared on the success path. If fetch() throws (network error/abort), timeoutId isn’t cleared, leaving a pending timer per request. Use a finally block (or try { ... } finally { clearTimeout(timeoutId) }) to always clear the timeout.
| const proxyRes = await fetch(`${backendUrl}/openai/responses/stream`, { | ||
| method: "POST", | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| "x-satoken": req.headers.get("x-satoken") || "", | ||
| }, | ||
| body: await proxyReq.text(), |
There was a problem hiding this comment.
The fallback proxy forwards the incoming token to the backend as x-satoken, but other backend calls in this PR use satoken (e.g. /auth/me). If the Java backend expects the same satoken header, this will break authenticated proxying. Consider mapping req.headers.get("x-satoken") to an outgoing satoken header (and omit the header entirely when missing).
| /** | ||
| * 服务端身份验证工具函数(仅用于 Next.js API Route / Server Component) | ||
| * | ||
| * 通过 x-satoken 请求头调用后端 /auth/me 验证身份, | ||
| * 返回 user_accounts.id(BigInt),匿名或 token 无效时返回 null。 | ||
| * | ||
| * 使用方:app/api/chat/route.ts、app/api/analytics/route.ts | ||
| */ | ||
| export async function resolveUserId(req: Request): Promise<bigint | null> { | ||
| const token = req.headers.get("x-satoken"); | ||
| if (!token || !process.env.BACKEND_URL) return null; | ||
| try { | ||
| const res = await fetch(`${process.env.BACKEND_URL}/auth/me`, { | ||
| headers: { satoken: token }, | ||
| }); |
There was a problem hiding this comment.
Docstring says this function calls the backend using the x-satoken header, but the implementation sends satoken. Please update the comment or the header name so the contract is unambiguous (especially since other routes in this PR use both header names).
| // 从请求头读取 satoken,转发给后端验证 | ||
| const token = request.headers.get("satoken"); | ||
| if (!token) { | ||
| return NextResponse.json({ error: "未授权访问" }, { status: 401 }); | ||
| } | ||
|
|
||
| if (!session?.user?.id) { | ||
| // 调用后端 /auth/me 验证 token(服务端直连后端,走 BACKEND_URL 环境变量) | ||
| const backendUrl = process.env.BACKEND_URL ?? "http://localhost:8080"; | ||
| const meRes = await fetch(`${backendUrl}/auth/me`, { | ||
| headers: { satoken: token }, |
There was a problem hiding this comment.
This route expects the client token in a satoken header, while other new client→Next routes use x-satoken (e.g. /api/chat, /api/analytics via resolveUserId). Standardizing on one header name for client→Next requests would reduce confusion and prevent accidental 401s when reusing fetch helpers.
| const params = await searchParams; | ||
| const redirectTarget = resolveRedirectTarget(params); | ||
|
|
||
| export default async function LoginPage({ searchParams: _ }: LoginPageProps) { |
There was a problem hiding this comment.
searchParams is no longer used, but the file still defines FALLBACK_CALLBACK_URL, coerceSearchParam, and resolveRedirectTarget, which will now be unused and trigger lint warnings. Consider removing the unused helpers and simplifying LoginPageProps accordingly.
| useEffect(() => { | ||
| // 状态确认后,未登录则跳转到登录页 | ||
| if (status === "unauthenticated") { | ||
| router.replace("/login?callbackUrl=/editor"); | ||
| } |
There was a problem hiding this comment.
This redirect still passes callbackUrl=/editor, but the updated login flow no longer uses callbackUrl/redirectTo and always starts OAuth via the backend. Consider removing this query param (or replacing it with whatever the new post-login navigation mechanism is) to avoid implying it’s respected.
No description provided.